package customer

import (
	"context"
	"fmt"
	"github.com/google/wire"
	imapp "go.manyji.com/im3/app"
	"go.manyji.com/im3/crypto"
	"go.manyji.com/lib/config"
	"go.manyji.com/lib/encoding/json"
	logger "go.manyji.com/lib/log"
	"go.manyji.com/lib/util/rand"
	"net/http"
	"sieve_admin_server/app/dao"
	"sieve_admin_server/app/dao/user"
	"sieve_admin_server/app/server"
	serviceLogger "sieve_admin_server/app/service/logger"
	serviceSys "sieve_admin_server/app/service/sys"
	"strconv"
	"strings"
	"sync"
	"time"
)

var (
	sessLock      = sync.Mutex{}
	sessions      = map[uint64]*user.UserAuth{}
	fSessExpire   = *config.Get("session.expire").(*int)
	fCookieName   = *config.Get("session.cookie").(*string)
	fDebugMode    = *config.Get("debug.enable").(*bool)
	fDebugSession = *config.Get("debug.session").(*string)
	fPathRoot     = *config.Get("web.pathroot").(*string)
	fTlsCert      = *config.Get("tls.cert").(*string)
	fTlsKey       = *config.Get("tls.key").(*string)
	UserAuthSet   = wire.NewSet(wire.Struct(new(UserAuthSrv), "*"))
)

type UserAuthSrv struct {
	LoginSrv     *serviceLogger.LoginSrv
	UserSrv      *UserSrv
	RoleSrv      *serviceSys.RoleSrv
	UserRepo     *dao.UserRepo
	UserAuthRepo *dao.UserAuthRepo
}

type loginToken struct {
	Uid uint64
}

func (a *UserAuthSrv) GetUserLoginState(uid uint64) string {

	if b := a.getSessionKey(int(uid), false); b != "" {
		return b
	}

	return ""
}

func (a *UserAuthSrv) GetLoginUid(r *http.Request) int {
	tk := a.getLoginToken(r)
	if tk == nil {
		return 0
	}
	return int(tk.Uid)
}

func (a *UserAuthSrv) getLoginToken(r *http.Request) *loginToken {
	token := ""
	if ck, err := r.Cookie("app" + strconv.Itoa(imapp.AppId()) + "_" + fCookieName); err == nil {
		token = ck.Value
	}
	if fDebugMode {
		if token == "" {
			token = r.PostForm.Get("_t")
		}
		if token == "" {
			token = r.URL.Query().Get("_t")
		}
	}
	if token == "" {
		return nil
	}
	ss := strings.SplitN(token, "-", 2)
	uid, err := strconv.Atoi(ss[0])
	if err != nil {
		return nil
	}
	if fDebugMode && fDebugSession != "" && ss[1] == fDebugSession {
		return &loginToken{Uid: uint64(uid)}
	}
	skey := a.getSessionKey(uid)
	if skey == "" {
		return nil
	}
	b, err := crypto.AesDecrypt(skey, ss[1])
	if err != nil {
		return nil
	}
	tk := &loginToken{}
	if err := json.Unmarshal(b, &tk); err != nil {
		return nil
	}
	if uint64(uid) != tk.Uid {
		return nil
	}
	return tk
}

func (a *UserAuthSrv) loginRecord(ctx context.Context, c *server.Context, userId uint64, account string, name, msg string) interface{} {

	item := map[string]interface{}{
		"name":          name,
		"login_account": account,
		"login_time":    time.Now().Unix(),
		"msg":           msg,
		"ip":            strings.Split(c.Http.Request.RemoteAddr, ":")[0],
	}

	return a.LoginSrv.Create(ctx, userId, item)
}

func (a *UserAuthSrv) Logout(ctx context.Context, c *server.Context, uid uint64) interface{} {

	var (
		logName = "退出日志"
		logMsg  = "退出成功"
		userId  = uid
	)

	//a.delExpireSession(uid)
	go func() {
		resp, err := a.UserSrv.GetUserInfo(ctx, userId)
		if err != nil {
			logger.Err(fmt.Sprintf("获取用户信息失败:%+v", err))
			return
		}

		if resp == nil {
			logger.Err(fmt.Sprintf("不存在的用户:%+v", userId))
			return
		}

		logger.Info(fmt.Sprintf("%+v", a.loginRecord(ctx, c, userId, resp.Account, logName, logMsg)))
	}()
	return nil
}

func (a *UserAuthSrv) Login(ctx context.Context, c *server.Context, item map[string]interface{}) interface{} {

	// 成功返回token，不成功返回空
	var (
		querySQL = fmt.Sprintf(`
			SELECT 
				*
			FROM user
			WHERE account = '%v' AND passwd = '%v' AND deleted = 0 ORDER BY create_at DESC LIMIT 1
`, item["username"], item["password"])
	)

	resp, err := a.UserRepo.QueryRaw(ctx, querySQL, user.User{})
	if err != nil {
		return err
	}

	var (
		respUser *user.User
		logName  = "登录日志"
		logMsg   = "登录成功"
		//userId   = uint64(c.Uid)
	)

	defer func() {
		go func() {
			if respUser != nil {
				userId := respUser.ID
				logger.Info("%+v", a.loginRecord(ctx, c, userId, item["username"].(string), logName, logMsg))
			}
		}()
	}()
	if len(resp) <= 0 {
		msg := "账号密码错误"
		logName, logMsg = "登录失败日志", msg
		return map[string]interface{}{
			"cookie": "",
			"msg":    "账号密码错误",
		}
	}

	respUser = resp[0].(*user.User)
	if respUser.Status == 2 {
		msg := "账户被冻结"
		logName, logMsg = "登录失败日志", msg
		return map[string]interface{}{
			"cookie": "",
			"msg":    "账户被冻结，不可登录。如需解冻，请联系上级",
		}
	}

	return a.createLoginToken(ctx, respUser.ID, c.Http.ResponseWriter)
}

func (a *UserAuthSrv) createLoginToken(ctx context.Context, uid uint64, w http.ResponseWriter) interface{} {
	str, err := a.generateToken(ctx, uid)
	if err != nil {
		return err
	}

	c := &http.Cookie{
		Name:     "app" + strconv.Itoa(imapp.AppId()) + "_" + fCookieName,
		Value:    fmt.Sprintf("%v", uid) + "-" + str,
		HttpOnly: false,
		Path:     fPathRoot,
		Secure:   fTlsCert != "" && fTlsKey != "",
	}
	http.SetCookie(w, c)

	return map[string]interface{}{
		"cookie": fmt.Sprintf("%v=%v", c.Name, c.Value),
	}
	//return fmt.Sprintf("%v=%v", c.Name, c.Value)
}

func (a *UserAuthSrv) generateToken(ctx context.Context, uid uint64) (string, error) {
	tk := &loginToken{Uid: uid}
	b, err := json.Marshal(tk)
	if err != nil {
		return "", err
	}
	sKey := rand.RandStr(32)
	if err := a.setSessionKey(ctx, uid, sKey); err != nil {
		return "", err
	}
	str, err := crypto.AesEncrypt(sKey, b)
	return str, err
}

func (a *UserAuthSrv) setSessionKey(ctx context.Context, uid uint64, sKey string) error {
	sessLock.Lock()
	defer sessLock.Unlock()
	var (
		tableName = user.UserAuth{}.TableName()
		et        = time.Now().Unix() + int64(fSessExpire)
	)
	if sess, ok := sessions[uid]; ok {

		updateSQL := fmt.Sprintf("UPDATE `%v` SET `session_key`='%v',`expire_time`=%v WHERE `id`=%v", tableName, sKey, et, sess.ID)
		if _, err := a.UserAuthRepo.ExecRaw(ctx, updateSQL); err != nil {
			return err
		}
		sessions[uid].SessionKey = sKey
		sessions[uid].ExpireTime = et
	} else {

		insertSQL := fmt.Sprintf("INSERT INTO `%v` (`uid`,`session_key`,`expire_time`) VALUES (%v,'%v',%v)", tableName, uid, sKey, et)
		res, err := a.UserAuthRepo.ExecRaw(ctx, insertSQL)
		if err != nil {
			return err
		}
		id, _ := res.LastInsertId()
		sessions[uid] = &user.UserAuth{
			ID:         uint64(id),
			Uid:        uid,
			SessionKey: sKey,
			ExpireTime: et,
		}
	}
	return nil
}

func (a *UserAuthSrv) getSessionKey(uid int, args ...interface{}) string {
	sessLock.Lock()
	defer sessLock.Unlock()
	sess, ok := sessions[uint64(uid)]
	if !ok {
		return ""
	}

	var (
		tableName = user.UserAuth{}.TableName()
	)

	if len(args) == 1 && args[0].(bool) == false {
		return sess.SessionKey
	}

	sess.ExpireTime = time.Now().Unix() + int64(fSessExpire)
	updateSQL := fmt.Sprintf("UPDATE `%v` SET `expire_time`=%v WHERE `id`=%v", tableName, sess.ExpireTime, sess.ID)
	if _, err := a.UserAuthRepo.ExecRaw(context.Background(), updateSQL); err != nil {
		return ""
	}

	return sess.SessionKey
}
